﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Server.Data;
using Server.Net;
using Server.Debug;

namespace Server.Core
{
    /// <summary>
    /// Class which hold and manipulates rooms and player list 
    /// </summary>
    public class ServerCore
    {
        private ArrayList _listRooms, _listPlayer, _listCards;
        private ServerNet _serverNet;
        private ServerDebug _serverDebug;
                
        private Int32 _count; //o sa-l folosesc pt a asigna id-uri la camere;

        /// <summary>
        /// The core object
        /// </summary>
        /// <param name="serverNet">Server Net object</param>
        public ServerCore(ServerNet serverNet)
        {
            _listRooms = new ArrayList();
            _listPlayer = new ArrayList();
            _listCards = new ArrayList();
            _serverDebug = new ServerDebug();
            _serverNet = serverNet;
            _count = 0;

            CityCard c = null;
            String[] cities = { City.AUGSBURG, City.BASEL, City.BUDWEIS, City.CARLSRUHE, City.FREIBURG, City.INGOLSTADT, 
                                  City.INNSBRUCK, City.KEMPTEN, City.LINZ, City.LODZ, City.MANNHEIM, City.MUNCHEN, City.NURENBERG, 
                                  City.PASSAU, City.PILSEN, City.REGENSBURG, City.SALZBURG, City.SIGMARINGEN, City.STUTTGART, 
                                  City.ULM, City.WURZBURG, City.ZURICH };

            for (int i = 0; i < cities.Length; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    c = new CityCard(cities[i]);
                    
                    _listCards.Add(c);
                }
            }
            
            _serverNet.ClientSocketError += new ClientSocketErrorHandler(_serverNet_ClientSocketError);
            _serverNet.Disconnect += new DisconnectHandler(_serverNet_Disconnect);
        }

        /// <summary>
        /// Function that is executed on disconnect event
        /// </summary>
        /// <param name="sender">Default</param>
        void _serverNet_Disconnect(object sender)
        {
            Player p;            
            for (int i = 0; i < _listPlayer.Count; i++)
            {
                p = (Player)_listPlayer[i];
                _serverNet.Send_Disconnect(p);
            }
        }

        /// <summary>
        /// Function that is executed on client error connection event and we delete the player that has this problem
        /// </summary>
        /// <param name="sender">Defaukt</param>
        /// <param name="player">Player</param>
        /// <param name="nickname">Nickname</param>
        void _serverNet_ClientSocketError(object sender, Player player, string nickname)
        {
            Room r;
            Player p;
            Int32 index;
            
            if (player.TableId != -1)
            {
                index = GetRoomPositionById(player.TableId);
                r = (Room)_listRooms[index];

                DeleteClient(player);
                if (isRoomReadyForDelete(r))
                {
                    for (int i = 0; i < 4; i++)
                    {
                        p = r.ListPlayer[i];
                        if (p != null)
                        {
                            DeleteClient(p);
                            _serverNet.Send_Disconnect(p);
                        }
                    }
                    _listRooms.RemoveAt(index);
                    _serverDebug.PrintDelRoom(r.Name);
                }
                else
                    AnnounceRoom(r, player.NickName);                
            }
            else
            {
                DeleteClientWithoutRoom(player);
            }
        }

        /// <summary>
        /// Announce a room when a client has quit
        /// </summary>
        /// <param name="table">Table object</param>
        /// <param name="nickname">Nickname of the client</param>
        public void AnnounceRoom(Room table, String nickname)
        {
            Player p;
            for (int i = 0; i < 4; i++)
            {
                p = table.ListPlayer[i];
                if (p != null)
                    _serverNet.Send_Quit(p, nickname);
            }
        }

        /// <summary>
        /// Delete a client that has problem with connection and isn't playing
        /// </summary>
        /// <param name="player">Player</param>
        public void DeleteClientWithoutRoom(Player player)
        {
            Int32 playerPosition = GetClientPosition(player.NickName);
            _listPlayer.RemoveAt(playerPosition);
            _serverDebug.PrintDelClient("null", player.NickName);
            BroadcastPlayers();
        }

        /// <summary>
        /// Delete a client from a room and from player list
        /// </summary>
        /// <param name="player">Player</param>
        public void DeleteClient(Player player)
        {
            Int32 index = GetRoomPositionById(player.TableId);
            Room r = (Room)_listRooms[index];
            Int32 playerRoomPos;
            Int32 playerPosition = GetClientPosition(player.NickName);
            
            playerRoomPos = r.GetPlayerPosition(player.NickName);
            r.ListPlayer[playerRoomPos] = null;
         
            _serverDebug.PrintDelClient(r.Name, player.NickName);
            _listPlayer.RemoveAt(playerPosition);
            _serverDebug.PrintDelClient("null", player.NickName);
            BroadcastPlayers();
         }

        /// <summary>
        /// Add a client to the list player
        /// </summary>
        /// <param name="nickname">The nickname of the player</param>
        /// <param name="ip">The ip of the player</param>
        /// <param name="port">The port listening of the player connected</param>
        public void AddClient(String nickname, String ip, String port)
        {
            Player p;
            Int32 _port = Convert.ToInt32(port);

            if (IsPlayerValid(nickname))
            {
                p = new Player();
                p.NickName = nickname;
                p.Ip = ip;
                p.Port = _port;
                
                _listPlayer.Add(p);
                _serverNet.Send_Offer(p, _listRooms);
                
                BroadcastPlayers();
                _serverDebug.PrintAddClient(p.NickName, p.Ip, p.Port);
            }
            else
            {
                _serverNet.Send_NACK_Offer(ip, _port);
            }            
        }
        
        /// <summary>
        /// Delete a player from server when we received quit message
        /// </summary>
        /// <param name="table_id">Table id</param>
        /// <param name="nickname">Nickname</param>
        public void DoDelete(Int32 table_id, String nickname)
        {
            Int32 roomPosition = GetRoomPositionById(table_id);
            Room r;
            Player p;
            p = GetPlayer(nickname);
            if (roomPosition != -1)
            {
                r = (Room)_listRooms[roomPosition];
                DeleteClient(p);
                AnnounceRoom(r, p.NickName);

                if (isRoomReadyForDelete(r))
                {
                    for (int i = 0; i < 4; i++)
                    {
                        p = r.ListPlayer[i];
                        if (p != null)
                        {
                            DeleteClient(p);
                            _serverNet.Send_Disconnect(p);
                        }
                    }
                    _listRooms.RemoveAt(roomPosition);
                    _serverDebug.PrintDelRoom(r.Name);
                    BroadcastRooms();
                }
            }
        }
        
        /// <summary>
        /// Create a room, add to the list room and send accept to deny to the player who requested creation
        /// </summary>
        /// <param name="table_name">The room name</param>
        /// <param name="nickname">The nickname of the player</param>
        public void CreateRoom(String table_name, String nickname)
        {
            Player p = GetPlayer(nickname);
            Room r;
            if (IsRoomValid(table_name))
            {
                _count++;
                r = new Room(table_name, _count);
                _listRooms.Add(r);
                r.ListPlayer[0] = p;
                p.TableId = _count;

                _serverNet.Send_ACK_CreateTable(p, _count);                    
                BroadcastRooms();
                _serverDebug.PrintCreateRoom(table_name);
            }
            else
            {
                _serverNet.Send_NACK_CreateTable(p);
            }

        }

        /// <summary>
        /// Valid join table request and send ack when the room has places open and nack otherwise.
        /// Announce the room that a client has join
        /// </summary>
        /// <param name="table_id">Table id</param>
        /// <param name="nickname">Nickname</param>
        public void UserJoinTable(Int32 table_id, String nickname)
        {
            Int32 roomPosition, index;
            Room r;
            Player p, x;
            Boolean _canJoin;
            roomPosition = GetRoomPositionById(table_id);

            r = (Room)_listRooms[roomPosition];
            p = GetPlayer(nickname);
            _canJoin = CanUserJoinRoom(roomPosition, nickname);

            if (p != null && roomPosition != -1 && _canJoin == true)
            {
                index = r.GetFirstFreePosition();
                if (index != -1)
                {
                    r.ListPlayer[index] = p;
                    p.TableId = table_id;
                    _serverNet.Send_ACK_JoinTable(p, table_id, _listRooms);
                    for (int i = 0; i < 4; i++)
                    {
                        x = r.ListPlayer[i];
                        if (x != null && i != index)
                        {
                            _serverNet.Send_User_Join_Table(x, nickname, index);
                        }

                    }
                    BroadcastRooms();
                    _serverDebug.PrintJoinUserRoom(r.Name, nickname);
                }
                else
                {
                    _serverNet.Send_NACK_Join_Table(p);
                }
            }
            else
            {
                _serverNet.Send_NACK_Join_Table(p);
            }
        }

        /// <summary>
        /// Set ready state on a client and announce the room.
        /// Also we check if all players from room are ready and start the game
        /// </summary>
        /// <param name="table_id">Table id</param>
        /// <param name="nickname">Nickname</param>
        public void SetUserReady(Int32 table_id, String nickname)
        {
            Int32 roomPosition, playerRoomPosition;
            Player p;
            Room r;

            roomPosition = GetRoomPositionById(table_id);

            r = (Room)_listRooms[roomPosition];
            playerRoomPosition = r.GetPlayerPosition(nickname);
            p = r.ListPlayer[playerRoomPosition];

            p.IsReady = true;

            for (int i =0; i < 4; i++)
            {
                p = r.ListPlayer[i];
                if (p != null)
                {
                    _serverNet.Send_User_Start(p, nickname);
                }
            }
            _serverDebug.PrintUserSetReady(r.Name, nickname);

            Boolean roomReady = IsRoomReady(roomPosition);

            if (roomReady)
            {
                r.IsGameStarted = true;
                r.PlayerTurn = 0;

                _serverDebug.PrintGameStart(r.Name);

                Player playerTurn = r.ListPlayer[0];
                ArrayList list = GetSixCards(_listCards);

                for (int i = 0; i < 4; i++)
                {
                    p = r.ListPlayer[i];
                    if (p != null)
                    {
                        _serverNet.Send_Game_Start(p, list);
                        _serverNet.Send_User_Turn(p, playerTurn.NickName);
                    }

                }
                BroadcastRooms();
            }
        }

        /// <summary>
        /// Cancel ready state of a player from room and announce the room
        /// </summary>
        /// <param name="table_id">Table id</param>
        /// <param name="nickname">Nickname</param>
        public void CancelUserReady(Int32 table_id, String nickname)
        {
            Int32 roomPosition, playerRoomPosition;
            Room r;
            Player p;
            roomPosition = GetRoomPositionById(table_id);            
            r = (Room)_listRooms[roomPosition];
            playerRoomPosition = r.GetPlayerPosition(nickname);

            p = r.ListPlayer[playerRoomPosition];
            p.IsReady = false;

            _serverDebug.PrintUserCancelReady(r.Name, nickname);

            for (int i = 0; i < 4; i++)
            {
                p = r.ListPlayer[i];
                if (p != null && i != playerRoomPosition)
                {
                    _serverNet.Send_User_Cancel(p, nickname);                        
                }
            }
        }

        /// <summary>
        /// Server received a move from a player and we process it. Depending on the type of the message we send all
        /// player the moved that the player had done
        /// </summary>
        /// <param name="move">Move</param>
        /// <param name="table_id">Table id</param>
        /// <param name="nickname">Nickname</param>
        /// <param name="data">Data. Here can be name for cards, cities or none</param>
        public void DoMove(Int16 move, Int32 table_id, String nickname, String data)
        {
            Int32 _roomPos = GetRoomPositionById(table_id);            
            Player p;
            Room r = (Room)_listRooms[_roomPos];
            Int32 _playerPos = r.GetPlayerPosition(nickname);

            for (int i = 0; i < 4; i++)
            {
                p = r.ListPlayer[i];
                if (p != null && i != _playerPos)
                {                    
                    switch(move){
                        case NetMessages.MESSAGE_CHANGE_CARDS:
                            _serverNet.Send_Change_Cards(p, nickname, data);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_CHANGE_CARDS, r.Name, nickname, p.NickName, data);
                            break;
                        case NetMessages.MESSAGE_DRAW_CARD:
                            _serverNet.Send_Draw_Card(p, nickname, data);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_DRAW_CARD, r.Name, nickname, p.NickName, data);
                            break;
                        case NetMessages.MESSAGE_PUT_CARD:
                            _serverNet.Send_Put_Card(p, nickname, data);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_PUT_CARD, r.Name, nickname, p.NickName, data);
                            break;
                        case NetMessages.MESSAGE_PUT_HOUSES:
                            _serverNet.Send_Put_Houses(p, nickname, data);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_PUT_HOUSES, r.Name, nickname, p.NickName, data);
                            break;
                        case NetMessages.MESSAGE_DISCARD_CARDS:
                            _serverNet.Send_Discard_Card(p, nickname, data);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_DISCARD_CARDS, r.Name, nickname, p.NickName, data);
                            break;
                        case NetMessages.MESSAGE_DISCARD_PUBLIC_CARDS:
                            _serverNet.Send_Discard_Public_Cards(p, nickname);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_DISCARD_PUBLIC_CARDS, r.Name, nickname, p.NickName, "");
                            break;
                        case NetMessages.MESSAGE_SCORE_HELP:
                            _serverNet.Send_Score_Help(p, nickname);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_SCORE_HELP, r.Name, nickname, p.NickName, "");
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Server received from a player a move which involves changing the turn
        /// </summary>
        /// <param name="move">Move</param>
        /// <param name="table_id">Table id</param>
        /// <param name="nickname">Nickname</param>
        public void DoTurnMove(Int16 move, Int32 table_id, String nickname)
        {
            Int32 _roomPos = GetRoomPositionById(table_id);
            
            Player p;
            Room r = (Room)_listRooms[_roomPos];
            Int32 _playerPos = r.GetPlayerPosition(nickname);

            //r.PlayerTurn = (Int16)((r.PlayerTurn+1)%r.ListPlayer.Count);

            p = r.ListPlayer[r.FindTurn()];
            String PlayerTurnNickname = p.NickName;

            for (int i = 0; i < 4; i++)
            {              
                p = r.ListPlayer[i];
                if (p!=null)
                    switch (move)
                    {
                        case NetMessages.MESSAGE_CONTINUE:
                            _serverNet.Send_User_Turn(p, PlayerTurnNickname);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_CONTINUE, r.Name, PlayerTurnNickname, p.NickName, "");
                            break;
                        case NetMessages.MESSAGE_SCORE:
                            if (!p.NickName.Equals(nickname))
                            {
                                _serverNet.Send_Score(p, nickname);
                                _serverDebug.PrintMove(NetMessages.MESSAGE_SCORE, r.Name, nickname, p.NickName, "");
                            }
                            _serverNet.Send_User_Turn(p, PlayerTurnNickname);
                            _serverDebug.PrintMove(NetMessages.MESSAGE_CONTINUE, r.Name, PlayerTurnNickname, p.NickName, "");
                            break;                        
                    }
            }
        }

        /// <summary>
        /// Get player based on a nickname
        /// </summary>
        /// <param name="nickname">Nickname</param>
        /// <returns>Player object</returns>
        private Player GetPlayer(String nickname)
        {
            Player p;

            for (int i = 0; i < _listPlayer.Count; i++)
            {
                p = (Player)_listPlayer[i];
                if (p.NickName.Equals(nickname))
                    return p;
            }
            return null;
        }

        /// <summary>
        /// Count cards from list that has a name. Is used to generate a valid six card list
        /// </summary>
        /// <param name="list">List</param>
        /// <param name="cardname">Card name</param>
        /// <returns></returns>        
        private Int32 CountCard(ArrayList list, String cardname)
        {
            Int32 _count = 0;
            CityCard c;

            for (int i = 0; i < list.Count; i++)
            {
                c = (CityCard)list[i];
                if (c.CityName.Equals(cardname))
                    _count++;
            }
            return _count;
        }

        /// <summary>
        /// Generate 6 card list when a game is started
        /// </summary>
        /// <param name="list">Array list with package card</param>
        /// <returns></returns>
        private ArrayList GetSixCards(ArrayList list)
        {            
            Random randomGen = new Random();
            ArrayList slist = new ArrayList();            
            CityCard c;
            Int16 _count = 0;
            Int32 index;

            while (_count != 6)
            {
                index = randomGen.Next(list.Count); 
                c = (CityCard)list[index];
                if (CountCard(slist, c.CityName) < 4)
                {
                    slist.Add(c);
                    _count++;
                }
            }            
            return slist;
        }

        /// <summary>
        /// Find the position of a player based on nickname
        /// </summary>
        /// <param name="nickname">Nickname</param>
        /// <returns>Positon from the player list</returns>
        private Int32 GetClientPosition(String nickname)
        {
            Player p;

            for (int i = 0; i < _listPlayer.Count; i++)
            {
                p = (Player)_listPlayer[i];
                if (p.NickName.Equals(nickname))
                    return i;
            }
            return -1;
        }
        
        /// <summary>
        /// Find the position of a room based on id
        /// </summary>
        /// <param name="table_id">Table id</param>
        /// <returns>The position from room list</returns>
        private Int32 GetRoomPositionById(Int32 table_id)
        {
            Room r;

            for (int i = 0; i < _listRooms.Count; i++)
            {
                r = (Room)_listRooms[i];                
                if (r.ID == table_id)
                    return i;
            }
            return -1;
        }

        /// <summary>
        /// Find the position of a room based on name
        /// </summary>
        /// <param name="table_name">Table name</param>
        /// <returns>The position from room list</returns>
        private Int32 GetRoomPositionByName(String table_name)
        {
            Room r;

            for (int i = 0; i < _listRooms.Count; i++)
            {
                r = (Room)_listRooms[i];
                if (r.Name.Equals(table_name))
                    return i;
            }
            return -1;
        }
        
        /// <summary>
        /// Check if a room is ready to start game
        /// </summary>
        /// <param name="roomPosition">Room position from room list</param>
        /// <returns>True if is ready</returns>
       
        private bool IsRoomReady(Int32 roomPosition)
        {
            Room r = (Room)_listRooms[roomPosition];
            Player p;

            for (int i = 0; i < 4; i++)
            {
                p = r.ListPlayer[i];
                if (p != null && !p.IsReady)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Check if a user that wants to connect is valid. We check if the nickname exists
        /// </summary>
        /// <param name="nickname">Nickname</param>
        /// <returns>True if the user is valid</returns>
        private bool IsPlayerValid(String nickname)
        {
            Player p;

            for (int i = 0; i < _listPlayer.Count; i++)
            {
                p = (Player)_listPlayer[i];
                if (p.NickName.Equals(nickname))
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Check if the name the room that need to be created is valid
        /// </summary>
        /// <param name="table_name">Table name</param>
        /// <returns>True if is valid</returns>
        private bool IsRoomValid(String table_name)
        {
            Room r;           
            for (int i = 0; i < _listRooms.Count; i++)
            {
                r = (Room)_listRooms[i];
                if (r.Name.Equals(table_name))
                {                    
                    return false;
                }
            }                 
            return true;
        }

        /// <summary>
        /// Check if a room can be deleted. The criterion by which we decide is:
        /// - if game isn't started and the player who created the room leave
        /// - if the number of player that are in the room is less than 2
        /// </summary>
        /// <param name="table">Table name</param>
        /// <returns>True if can be deleted</returns>
        private bool isRoomReadyForDelete(Room table)
        {
            Room r = table;         
            
            if (r.GetFirstFreePosition() == 0 && !r.IsGameStarted)
                return true;

            if (r.GetNumberOfPlayer() < 2)
                return true;
            return false;
        }

        /// <summary>
        /// Check if a player can join in the room. 
        /// He can join when the game hasn't started and when in the room are less than 4 players.
        /// </summary>
        /// <param name="roomPosition"></param>
        /// <param name="nickname"></param>
        /// <returns></returns>
        private bool CanUserJoinRoom(Int32 roomPosition, String nickname)
        {
            Room r = (Room)_listRooms[roomPosition];
           
            if (r.IsGameStarted)
                return false;

            if (r.GetFirstFreePosition() == -1)
                return false;
            return true;
        }

        /// <summary>
        /// Send to all players connected list of rooms
        /// </summary>
        private void BroadcastRooms()
        {
            Player p;

            for (int i = 0; i < _listPlayer.Count; i++)
            {
                p = (Player)_listPlayer[i];
                _serverNet.Send_Offer(p, _listRooms);
            }
        }

        /// <summary>
        /// Send to all players connect list of players
        /// </summary>
        private void BroadcastPlayers()
        {
            Player p;

            for (int i = 0; i < _listPlayer.Count; i++)
            {
                p = (Player)_listPlayer[i];
                _serverNet.Send_Players(p, _listPlayer);
            }
        }
    }
}
